package test.com.spring.lo;

import com.mysql.cj.util.StringUtils;
import com.spring.lo.ioc.BeanDefinition;
import com.spring.lo.ioc.PropertyValue;
import com.spring.lo.ioc.RuntimeBeanReference;
import com.spring.lo.ioc.TypedStringValue;
import com.spring.lo.po.User;
import com.spring.lo.service.UserServiceImpl;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Spring框架手写测试。从原始版本慢慢进化到仿照 spring管理
 * @author zhangbh
 * 2023-05-05 08:23
 **/
public class SpringTestV4 {

    /**
     * 存放单例bean容器
     * key=beanName,value=Bean实例
     */
    private Map<String, Object> singletonObjects = new HashMap<>();

    /**
     * 存放Bean元信息对应关系
     * key=beanName,value=BeanDefinition
     */
    private Map<String, BeanDefinition> beanDefinitions = new HashMap<>();

    @Before
    public void init() {
        // 加载配置文件中的bean元信息
        String location = "bean.xml";
        // 获取文件流
        InputStream in = getResourceAsInputStream(location);
        // 通过文件流获取Document对象
        Document document = getDocument(in);
        // 通过Document对象解析BeanDefinition
        loadBeanDefinitions(document);
    }

    /**
     * 版本4 使用面向过程的思维，将实例的元信息加载和获取拆分，并采用配置文件形式注册到容器
     * 大体分为两步：
     * 1.启动前将所有的配置Bean加载成 BeanDefinition，并放入容器中
     * 2.调用getBean从容器中加载Bean实例
     */
    @Test
    public void findUserV4() {
        UserServiceImpl userService = (UserServiceImpl) getBean("userService");

        //实现用户查询功能
        Map<String, Object> map = new HashMap<>();
        map.put("username", "王五");
        List<User> users = userService.queryUsers(map);
        System.out.println(users);
    }

    private void loadBeanDefinitions(Document document) {
        // 通过预定义的风格读取xml文件，并创建BeanDefinition
        Element rootElement = document.getRootElement();
        if (!rootElement.getName().equals("beans")) {
            return;
        }
        List<Element> elements = rootElement.elements();
        for (Element element : elements) {
            if (element.getName().equals("bean")) {
                parseBeanDefinition(element);
            } else {
                parseCustom(element);
            }
        }
    }

    private void parseCustom(Element element) {

    }

    private void parseBeanDefinition(Element element) {
        // 设置普通属性 id name scope init-method
        String clazz = element.attributeValue("class");
        if (StringUtils.isNullOrEmpty(clazz)) {
            System.out.println("!!!!!元素class为空，跳过加载!!!!");
            return;
        }
        String id = element.attributeValue("id");
        if (StringUtils.isNullOrEmpty(id)) {
            System.out.println("!!!!!元素id为空，跳过加载!!!!");
            return;
        }

        BeanDefinition bd = new BeanDefinition(id, clazz);
        String scope = element.attributeValue("scope");
        if (StringUtils.isNullOrEmpty(scope)) {
            scope = "singleton";
        }
        bd.setScope(scope);
        String name = element.attributeValue("name");
        if (StringUtils.isNullOrEmpty(name)) {
            name = id;
        }
        bd.setBeanName(name);
        String initMethod = element.attributeValue("init-method");
        bd.setInitMethod(initMethod);
        // 设置属性 properties
        parseProperties(bd, element);
        beanDefinitions.put(bd.getBeanName(), bd);
    }

    private void parseProperties(BeanDefinition bd, Element element) {
        List<Element> propertyList = element.elements("property");
        Class<?> classType = bd.getClassType();
        for (Element property : propertyList) {
            String name = property.attributeValue("name");
            if (StringUtils.isNullOrEmpty(name)) {
                continue;
            }
            String value = property.attributeValue("value");
            String ref = property.attributeValue("ref");
            if (StringUtils.isNullOrEmpty(value) && StringUtils.isNullOrEmpty(ref)) {
                System.out.println(name + "属性的值为空!!!!!");
                continue;
            }
            PropertyValue pv = null;
            if (StringUtils.isNullOrEmpty(value)) {
                // 加载引用值
                RuntimeBeanReference reference = new RuntimeBeanReference(ref);
                pv = new PropertyValue(name, reference);
            } else {
                // 加载普通值
                // 通过属性名获取实际类型
                Class<?> fieldType = null;
                try {
                    Field field = classType.getDeclaredField(name);
                    field.setAccessible(true);
                    fieldType = field.getType();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                    System.out.printf("加载属性%s错误%n", name);
                    continue;
                }
                TypedStringValue realValue = new TypedStringValue(value, fieldType);
                pv = new PropertyValue(name, realValue);
            }
            bd.addPropertyValue(pv);
        }
    }

    private Document getDocument(InputStream in) {
        SAXReader reader = new SAXReader();
        try {
            return reader.read(in);
        } catch (DocumentException e) {
            throw new RuntimeException("xml文件读取异常", e);
        }
    }

    private InputStream getResourceAsInputStream(String location) {
        return this.getClass().getClassLoader().getResourceAsStream(location);
    }


    /**
     * 获取Bean实例
     * @param beanName beanName
     * @return 对应的对象
     */
    private Object getBean(String beanName) {
        // 1.尝试从容器中获取
        Object bean = singletonObjects.get(beanName);
        // 2.当获取到直接返回
        if (bean != null) {
            return bean;
        }
        BeanDefinition bd = beanDefinitions.get(beanName);
        if (bd == null) {
            System.out.println("加载不到类声明,beanName=" + beanName);
            return null;
        }
        // 3.获取不到，则从BeanDefinition中解析元信息，创建Bean实例
        bean = loadBean(bd);
        return bean;
    }

    private Object loadBean(BeanDefinition bd) {
        Object bean;
        if (bd.isSingleton()) {
            bean = createBean(bd);
            singletonObjects.put(bd.getBeanName(), bean);
        } else if (bd.isPrototype()) {
            bean = createBean(bd);
        } else {
            bean = createBean(bd);
        }
        return bean;
    }

    private Object createBean(BeanDefinition bd) {
        // 创建Bean三部曲
        // a.创建Bean对象；
        Class<?> classType = bd.getClassType();
        Object bean;
        try {
            bean = classType.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("创建bean实例异常", e);
        }
        // b.注入Bean属性；
        String initMethod = bd.getInitMethod();
        loadProperty(bean, bd);
        if (StringUtils.isNullOrEmpty(initMethod)) {
            return bean;
        }
        // c.执行 Bean init方法
        try {
            Method method = classType.getMethod(initMethod);
            // 暴力破解
            method.setAccessible(true);
            method.invoke(bean);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("获取初始化方法异常", e);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException("初始化方法执行异常", e);
        }
        return bean;
    }

    private void loadProperty(Object bean, BeanDefinition bd) {
        List<PropertyValue> pvs = bd.getPropertyValues();
        Class<?> classType = bd.getClassType();
        for (PropertyValue pv : pvs) {
            String name = pv.getName();
            Object value = pv.getValue();
            if (value == null) {
                return;
            }
            Object valueToUse = null;
            // 判断值类型
            if (value instanceof TypedStringValue) {
                valueToUse = parseValue((TypedStringValue) value);
            } else if (value instanceof RuntimeBeanReference) {
                // todo 这边会有循环依赖的隐患
                String ref = ((RuntimeBeanReference) value).getRef();
                valueToUse = getBean(ref);
            }
            try {
                Field field = classType.getDeclaredField(name);
                field.setAccessible(true);
                field.set(bean, valueToUse);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException("获取字段异常", e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("设置字段异常", e);
            }
        }
    }

    private Object parseValue(TypedStringValue ts) {
        Class<?> targetType = ts.getTargetType();
        String value = ts.getValue();
        Object result = null;
        // 基础类型转换
        if (targetType == String.class) {
            return value;
        } else if (targetType == Integer.class) {
            result = Integer.parseInt(value);
        } else if (targetType == Double.class) {
            result = Double.valueOf(value);
        }
        // todo 这边还有很多类型，一样的判断，就不写了
        return result;
    }
}
